//
//  NSObject+Listener.m
//  YanXiuWang
//
//  Created by 慧趣小歪 on 16/12/8.
//  Copyright © 2016年 小分队. All rights reserved.
//

#import "NSObject+Listener.h"
#import <objc/runtime.h>

#define kListenerArray "controller_listener_array"
typedef NSString * NSKeyValueChangeKey;
@class ListenerDataDealoc;

@interface Listener ()

@property (nonatomic, assign) BOOL nilDataSource;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, weak) ListenerDataDealoc *listener;
@property (nonatomic, unsafe_unretained) NSObject *dataSource;
@property (nonatomic, weak) NSObject *listenerVC;
@property (nonatomic, assign) SEL callback;
@property (nonatomic, assign) NSKeyValueObservingOptions options;

@end

@interface ListenerDataDealoc : NSObject

@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, unsafe_unretained) NSObject *dataSource;
@property (nonatomic, weak) Listener *listener;

@end



@interface NSObject ()

@property (nonatomic, readonly) NSMutableArray *listeners;

@end

@implementation NSObject (Listener)

- (void)cleanListeners {
    objc_setAssociatedObject(self, kListenerArray, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSMutableArray *)listeners {
    NSMutableArray *array = objc_getAssociatedObject(self, kListenerArray);
    if (array == nil) {
        array = [NSMutableArray array];
        objc_setAssociatedObject(self, kListenerArray, array, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return array;
}
- (Listener *)listen:(NSObject *)obj forKey:(SEL)key onChanged:(SEL)callback; {
    return [self listen:obj forKeyPath:NSStringFromSelector(key) onChanged:callback];
}
- (Listener *)listen:(NSObject *)obj forKey:(SEL)key withOptions:(NSKeyValueObservingOptions)options onChanged:(SEL)callback {
    return [self listen:obj forKeyPath:NSStringFromSelector(key) withOptions:options onChanged:callback];
}
- (Listener *)listen:(NSObject *)obj forKeyPath:(NSString *)keyPath onChanged:(SEL)callback{
    return [self listen:obj forKeyPath:keyPath withOptions:NSKeyValueObservingOptionNew onChanged:callback];
}
- (Listener *)listen:(NSObject *)obj forKeyPath:(NSString *)keyPath withOptions:(NSKeyValueObservingOptions)options onChanged:(SEL)callback {
    Listener *dkvo = [Listener new];
    dkvo.dataSource = obj;
    dkvo.listenerVC = self;
    dkvo.keyPath = keyPath;
    dkvo.callback = callback;
    dkvo.options = options;
    
    [self.listeners addObject:dkvo];
    
    if (self != obj) {
        ListenerDataDealoc *dDealoc = [ListenerDataDealoc new];
        dDealoc.listener = dkvo;
        dDealoc.dataSource = obj;
        dDealoc.keyPath = keyPath;
        
        [obj.listeners addObject:dDealoc];
    }
    
    [obj addObserver:dkvo forKeyPath:keyPath options:options context:nil];
    return dkvo;
}

@end

@implementation ListenerDataDealoc

- (void)dealloc {
    if (_listener == nil) return;
    [_dataSource removeObserver:_listener forKeyPath:_keyPath];
    _dataSource = nil;
    NSInteger index = [_listener.listenerVC.listeners indexOfObject:_listener];
    if (index != NSNotFound) {
        _listener.dataSource = nil;
        [_listener.listenerVC.listeners removeObject:_listener];
    }
}

@end

@implementation Listener

- (dispatch_block_t)nothingOnRelease {
    _nilDataSource = NO;
    return ^{};
}

- (dispatch_block_t)nilDataSourceOnRelease {
    _nilDataSource = YES;
    return ^{};
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    
    if ([keyPath isEqualToString:_keyPath]) {
        // 如果监听多值则直接返回change
        BOOL moreValue = (_options & (NSKeyValueObservingOptionOld      |
                                      NSKeyValueObservingOptionInitial  |
                                      NSKeyValueObservingOptionPrior)
                          ) > 0;
        id obj = moreValue ? change.copy : change[NSKeyValueChangeNewKey]; // [_dataSource valueForKeyPath:keyPath];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        
        [_listenerVC performSelector:_callback withObject:obj];

#pragma clang diagnostic pop
    }
}

- (void)dealloc {
    if (_dataSource == nil) return;
    [_dataSource removeObserver:self forKeyPath:_keyPath];
    [_listenerVC.listeners removeObject:self];
    if (_nilDataSource) [_dataSource setValue:nil forKeyPath:_keyPath];
    [_dataSource.listeners removeObject:_listener];
}

@end
//  SEL 转 obj @selector()
//    NSValue *selectorObj = [NSValue valueWithBytes:&callback objCType:@encode(SEL)];

